﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Internal;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition.ReflectionModel
{
    internal class ImportingMember : ImportingItem
    {
        private readonly ReflectionWritableMember _member;

        public ImportingMember(ContractBasedImportDefinition definition, ReflectionWritableMember member)
            : base(definition, member)
        {
            Assumes.NotNull(definition, member);

            _member = member;
        }

        public CompositionResult SetValue(object instance, object value)
        {
            if (this.Definition.Cardinality == ImportCardinality.ZeroOrMore)
            {
                return this.SetCollectionMemberValue(instance, this.ImportType.ElementType, value as IEnumerable);
            }
            else
            {
                return this.SetSingleMemberValue(instance, value);
            }
        }

        private CompositionResult SetCollectionMemberValue(object instance, Type itemType, IEnumerable values)
        {
            Assumes.NotNull(values);
            var result = CompositionResult.SucceededResult;

            if (this._member.CanWrite && this.ImportType.Type.IsAssignableFrom(values.GetType()))
            {
                return this.SetSingleMemberValue(instance, values);
            }

            object collectionValue = this._member.GetValue(instance);
            if (collectionValue == null)
            {
                var issue = CompositionIssue.CreateIssueAsError(
                    CompositionIssueId.ImportCollectionFailedCollectionNull,
                    Strings.ImportCollectionFailedCollectionNull,
                    instance.GetType().FullName,
                    this._member.Name,
                    this.Definition.ContractName,
                    itemType.Name,
                    values.GetType().Name);

                return result.MergeIssue(issue);
            }

            Type colType = collectionValue.GetType();

            MethodInfo clear = colType.GetMethod("Clear");
            MethodInfo add = colType.GetMethod("Add");

            if (!CollectionServices.IsCollectionType(this.ImportType.Type, out itemType)
                || clear == null || add == null)
            {
                var issue = CompositionIssue.CreateIssueAsError(CompositionIssueId.ImportCollectionFailedCollectionNotWritable
                    , Strings.ImportCollectionFailedCollectionNotWritable
                    , instance.GetType().FullName
                    , this._member.Name
                    , this.Definition.ContractName
                    , this.ImportType.Type.Name);

                return result.MergeIssue(issue);
            }

            clear.Invoke(collectionValue, new object[0]);
            foreach (object value in values)
            {
                add.Invoke(collectionValue, new object[] { value });
            }
            return result;
        }

        private CompositionResult SetSingleMemberValue(object instance, object value)
        {
            var result = CompositionResult.SucceededResult;

            if (!this._member.CanWrite)
            {
                return result.MergeIssue(CompositionIssue.CreateIssueAsError(
                    CompositionIssueId.ImportSingleValueFailedNotWritable,
                    Strings.ImportSingleValueFailedNotWritable,
                    instance.GetType().FullName,
                    this._member.Name,
                    this.Definition.ContractName,
                    this.ImportType.Type.Name));
            }

            try
            {
                this._member.SetValue(instance, value);
            }
            catch (TargetInvocationException ex)
            {
                var issue = CompositionIssue.CreateIssueAsError(
                    CompositionIssueId.ExceptionDuringSettingOfImport,
                    ex.InnerException,
                    Strings.ExceptionDuringSettingOfImport,
                    this._member.Name,
                    this._member.DeclaringType.FullName);

                issue.Details[CompositionIssueDetailKey.ContractName] = this.Definition.ContractName;
                issue.Details[CompositionIssueDetailKey.ImportNode] = this;
                issue.Details[CompositionIssueDetailKey.ComponentInstance] = instance;
                issue.Details[CompositionIssueDetailKey.ValueSetting] = value;
                return result.MergeIssue(issue);
            }

            return result;
        }
    }
}
